package dd.lo.chapter22;

import dd.lo.util.ImageUtils;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.opencv.objdetect.Objdetect;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class FaceDetector extends Application {
    private static final int DEFAULT_WIDTH = 1200;
    private static final int DEFAULT_HEIGHT = 900;

    private static final int SHOW_IMG_WIDTH = 600;
    private static final int SHOW_IMG_HEIGHT = 450;

    private static final double DEFAULT_RATIO = (double) DEFAULT_WIDTH / DEFAULT_HEIGHT;

    private Application app;

    private ImageView imageView;

    private void showImage(Mat img, String winTitle) {
        //缩放、转码
        ImageUtils.fitImgSize(img, SHOW_IMG_WIDTH, SHOW_IMG_HEIGHT);
        MatOfByte buffer = new MatOfByte();
        Imgcodecs.imencode(".jpeg", img, buffer);
        ImageView view = new ImageView();
        view.setImage(new Image(new ByteArrayInputStream(buffer.toArray())));
        //贴到组件上
        VBox vBox = new VBox();
        vBox.getChildren().addAll(view);
        Scene scene = new Scene(vBox, SHOW_IMG_WIDTH, SHOW_IMG_HEIGHT);
        Stage newWindow = new Stage();
//        newWindow.initModality(Modality.APPLICATION_MODAL); // 设置此模式，则窗口打开时，其他窗口无法操作
        newWindow.initModality(Modality.NONE);
        newWindow.setTitle(winTitle);
        newWindow.setScene(scene);
        newWindow.show();
    }

    public static void start(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        app = this;
        Button exitBtn = new Button("关闭");
        exitBtn.setOnMouseClicked(e -> {
            Platform.exit();
        });
        Button fileChooserBtn = new Button("选择文件");
        fileChooserBtn.setOnMouseClicked(e -> {
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("请选择含有人脸的合照");
            fileChooser.setInitialDirectory(new File(System.getProperty("user.home") + "/Downloads")
            );
            fileChooser.getExtensionFilters().addAll(
                    new FileChooser.ExtensionFilter("All Images", "*.*"),
                    new FileChooser.ExtensionFilter("JPEG", "*.jpeg"),
                    new FileChooser.ExtensionFilter("WEBP", "*.webp"),
                    new FileChooser.ExtensionFilter("PNG", "*.png"),
                    new FileChooser.ExtensionFilter("JPG", "*.jpg"),
                    new FileChooser.ExtensionFilter("GIF", "*.gif"),
                    new FileChooser.ExtensionFilter("BMP", "*.bmp")
            );
            File imgFile = fileChooser.showOpenDialog(stage);
            if (null == imgFile) {
                System.out.println("用户取消选中文件");
                return;
            }
            //读取一张图片
            Mat srcImg = Imgcodecs.imread(imgFile.getAbsolutePath(), Imgcodecs.IMREAD_UNCHANGED);
            if (srcImg.empty()) {
                throw new RuntimeException("图片读取失败");
            }
            detectFace(srcImg);
        });
        FlowPane bottomControl = new FlowPane();
        bottomControl.getChildren().addAll(fileChooserBtn, exitBtn);
        bottomControl.setPrefHeight(50);
        BorderPane root = new BorderPane();
        imageView = new ImageView();//imageView放到一个pane的中间
        root.setCenter(imageView);
        root.setBottom(bottomControl);
        Scene scene = new Scene(root, DEFAULT_WIDTH, DEFAULT_HEIGHT + 50);
        stage.setScene(scene);
        stage.setTitle("Example1-Face Detect");
        stage.setResizable(false);
        stage.show();
    }

    private void detectFace(Mat srcImg) {
        Task<Void> task = new Task<Void>() {
            @Override
            protected Void call() {
                long startTime = System.currentTimeMillis();
                double ratio = (double) srcImg.width() / srcImg.height();
                Mat grayImg = new Mat();
                Imgproc.cvtColor(srcImg, grayImg, Imgproc.COLOR_BGR2GRAY);
//                Imgproc.equalizeHist(grayImg, grayImg);
                Platform.runLater(() -> showImage(new Mat(grayImg, new Rect(0, 0, grayImg.width(), grayImg.height())), "灰度图"));
                MatOfRect results = new MatOfRect();
                CascadeClassifier faceClassifier = new CascadeClassifier( app.getClass().getResource("").getPath() + "haarcascade_frontalface_default.xml");
//                CascadeClassifier faceClassifier = new CascadeClassifier( app.getClass().getResource("").getPath() + "haarcascade_frontalface_alt.xml");
                CascadeClassifier eyeClassifier = new CascadeClassifier( app.getClass().getResource("").getPath() + "haarcascade_eye.xml");
                int minWidth = (int) Math.round((double) srcImg.height() * 0.05);
                int minHeight = (int) Math.round((double) srcImg.height() * 0.05);
                faceClassifier.detectMultiScale(grayImg, results, 1.1, 3, Objdetect.CASCADE_DO_CANNY_PRUNING, new Size(30, 30));
                long endTime = System.currentTimeMillis();
                System.out.printf("Time cost: %dms\n", endTime - startTime);
                Rect[] faceRectangles = results.toArray();
                for (Rect faceRectangle : faceRectangles) {
                    //画出识别结果
                    Imgproc.rectangle(srcImg, faceRectangle, new Scalar(0, 255, 0), 2);
//                    Imgproc.rectangle(srcImg, new Rect((int) (faceRectangle.x - faceRectangle.width * 0.1), (int) (faceRectangle.y - faceRectangle.height * 0.4), (int) (faceRectangle.width * 1.2), (int) (faceRectangle.height * 1.8)), new Scalar(0, 255, 0), 3);
                    Mat faceRoi = new Mat(grayImg, faceRectangle);
                    MatOfRect eyes = new MatOfRect();
                    eyeClassifier.detectMultiScale(faceRoi, eyes, 1.1);
                    Rect[] eyeRectangles = eyes.toArray();
                    if (null != eyeRectangles && eyeRectangles.length >= 2) {
                        for (Rect eyeRectangle : eyeRectangles) {
                            Imgproc.rectangle(srcImg, new Rect(faceRectangle.x + eyeRectangle.x, faceRectangle.y + eyeRectangle.y, eyeRectangle.width, eyeRectangle.height), new Scalar(0, 0, 255), 2);
                        }
                        //画出通过眼睛识别的结果
                        Imgproc.rectangle(srcImg, faceRectangle, new Scalar(0, 255, 0), 2);
                    }
                }
                //调整图片大小以用于展示
                Size imgFitSize = new Size(DEFAULT_HEIGHT * ratio, DEFAULT_HEIGHT);
                double fitRatio = imgFitSize.width / imgFitSize.height;
                if (fitRatio > DEFAULT_RATIO) {
                    imgFitSize = new Size(DEFAULT_WIDTH, DEFAULT_WIDTH / ratio);
                }
                Imgproc.resize(srcImg, srcImg, imgFitSize);
                //将最终结果展示出来
                MatOfByte buffer = new MatOfByte();
                Imgcodecs.imencode(".jpeg", srcImg, buffer);
                imageView.setImage(new Image(new ByteArrayInputStream(buffer.toArray())));
                return null;
            }
        };
        new Thread(task).start();
    }
}
